package com.crazymaker.springcloud.base.security.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.crazymaker.springcloud.base.security.token.JwtAuthenticationToken;
import com.crazymaker.springcloud.common.constants.SessionConstants;
import com.crazymaker.springcloud.common.context.SessionHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private RequestMatcher requiresAuthenticationRequestMatcher;

    private List<RequestMatcher> permissiveRequestMatchers;

    private AuthenticationManager authenticationManager;

    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();

    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

    public JwtAuthenticationFilter() {
        requiresAuthenticationRequestMatcher = new RequestHeaderRequestMatcher(SessionConstants.AUTHORIZATION_HEAD);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (!requiresAuthentication(request, response)) {
            filterChain.doFilter(request, response);
            return;
        }

        JwtAuthenticationToken passedToken = null;
        AuthenticationException failed = null;

        String token = null;

        String sessionIDStore = SessionHolder.getSessionIDStore();
        if (sessionIDStore.equals(SessionConstants.SESSION_STORE)) {
            // 前端token
            token = request.getHeader(SessionConstants.AUTHORIZATION_HEAD);
        } else if (sessionIDStore.equals(SessionConstants.ADMIN_AUTHORIZATION_HEAD)) {
            // 管理端token
            token = request.getHeader(SessionConstants.ADMIN_AUTHORIZATION_HEAD);
        } else {
            failed = new InsufficientAuthenticationException("请求头认证信息为空");
            unsuccessfulAuthentication(request, response, failed);
            return;
        }

        token = StringUtils.removeStart(token, "Bearer ");

        try {
            if (StringUtils.isNotBlank(token)) {
                // 构建jwt token
                JwtAuthenticationToken jwtToken = new JwtAuthenticationToken(JWT.decode(token));
                passedToken = (JwtAuthenticationToken) authenticationManager.authenticate(jwtToken);
                UserDetails userDetails = (UserDetails) passedToken.getDetails();
                request.setAttribute(SessionConstants.USER_IDENTIFIER, userDetails);
            } else {
                failed = new InsufficientAuthenticationException("请求头认证消息为空");
            }
        } catch (JWTDecodeException exception) {
            logger.error("jwt format error", exception);
            failed = new InsufficientAuthenticationException("请求头认证信息有误", exception);
        } catch (InternalAuthenticationServiceException exception) {
            logger.error(
                    "An internal error occurred while trying to authenticate the user.",
                    failed);
            failed = exception;
        } catch (AuthenticationException e) {
            failed = e;
        }

        if (passedToken != null) {
            successfulAuthentication(request, response, passedToken);
        } else if (!permissiveRequest(request)) {
            unsuccessfulAuthentication(request, response, failed);
            return;
        }


        filterChain.doFilter(request, response);
    }

    @Override
    public void afterPropertiesSet() throws ServletException {
        Assert.notNull(authenticationManager, "AuthenticationManager must be not null");
        Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher must be null");
        Assert.notNull(permissiveRequestMatchers, "permissiveRequestMatchers must be not null");
    }

    public AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    public boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        return requiresAuthenticationRequestMatcher.matches(request);
    }

    public boolean permissiveRequest(HttpServletRequest request) {
        if (permissiveRequestMatchers == null)
            return false;
        for (RequestMatcher matcher : permissiveRequestMatchers) {
            if (matcher.matches(request)) {
                return true;
            }
        }
        return false;
    }

    public void setPermissiveUrl(String... urls) {
        if (null == permissiveRequestMatchers) {
            permissiveRequestMatchers = new ArrayList<>();
        }
        for (String url : urls) {
            permissiveRequestMatchers.add(new AntPathRequestMatcher(url));
        }
    }

    public AuthenticationSuccessHandler getSuccessHandler() {
        return successHandler;
    }

    public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
        Assert.notNull(successHandler, "SuccessHandler must be not null");
        this.successHandler = successHandler;
    }

    public AuthenticationFailureHandler getFailureHandler() {
        return failureHandler;
    }

    public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "FailureHandler must be not null");
        this.failureHandler = failureHandler;
    }

    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failException)
            throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, failException);
    }

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication resultAuth)
            throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(resultAuth);
        successHandler.onAuthenticationSuccess(request, response, resultAuth);
    }
}
